/****************************************************************************** * Spine Runtimes Software License v2.5 * * Copyright (c) 2013-2016, Esoteric Software * All rights reserved. * * You are granted a perpetual, non-exclusive, non-sublicensable, and * non-transferable license to use, install, execute, and perform the Spine * Runtimes software and derivative works solely for personal or internal * use. Without the written permission of Esoteric Software (see Section 2 of * the Spine Software License Agreement), you may not (a) modify, translate, * adapt, or develop new applications using the Spine Runtimes or otherwise * create derivative works or improvements of the Spine Runtimes or (b) remove, * delete, alter, or obscure any trademarks or any copyright, trademark, patent, * or other intellectual property or proprietary rights notices on or in the * Software, including any copy thereof. Redistributions in binary or source * form must include this license and terms. * * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ #define SPINE_OPTIONAL_NORMALS using UnityEngine; namespace Spine.Unity.MeshGeneration { public class ArraysMeshGenerator { #region Settings public bool PremultiplyVertexColors { get; set; } protected bool addNormals; public bool AddNormals { get { return addNormals; } set { addNormals = value; } } protected bool addTangents; public bool AddTangents { get { return addTangents; } set { addTangents = value; } } #endregion protected float[] attachmentVertexBuffer = new float[8]; protected Vector3[] meshVertices; protected Color32[] meshColors32; protected Vector2[] meshUVs; #if SPINE_OPTIONAL_NORMALS protected Vector3[] meshNormals; #endif protected Vector4[] meshTangents; protected Vector2[] tempTanBuffer; public void TryAddNormalsTo (Mesh mesh, int targetVertexCount) { #if SPINE_OPTIONAL_NORMALS if (addNormals) { bool verticesWasResized = this.meshNormals == null || meshNormals.Length < targetVertexCount; if (verticesWasResized) { this.meshNormals = new Vector3[targetVertexCount]; Vector3 fixedNormal = new Vector3(0, 0, -1f); Vector3[] normals = this.meshNormals; for (int i = 0; i < targetVertexCount; i++) normals[i] = fixedNormal; } mesh.normals = this.meshNormals; } #endif } /// Ensures the sizes of the passed array references. If they are not the correct size, a new array will be assigned to the references. /// true, if a resize occurred, false otherwise. public static bool EnsureSize (int targetVertexCount, ref Vector3[] vertices, ref Vector2[] uvs, ref Color32[] colors) { Vector3[] verts = vertices; bool verticesWasResized = verts == null || targetVertexCount > verts.Length; if (verticesWasResized) { // Not enough space, increase size. vertices = new Vector3[targetVertexCount]; colors = new Color32[targetVertexCount]; uvs = new Vector2[targetVertexCount]; } else { // Too many vertices, zero the extra. Vector3 zero = Vector3.zero; for (int i = targetVertexCount, n = verts.Length; i < n; i++) verts[i] = zero; } return verticesWasResized; } public static bool EnsureTriangleBuffersSize (ExposedList submeshBuffers, int targetSubmeshCount, SubmeshInstruction[] instructionItems) { bool submeshBuffersWasResized = submeshBuffers.Count < targetSubmeshCount; if (submeshBuffersWasResized) { submeshBuffers.GrowIfNeeded(targetSubmeshCount - submeshBuffers.Count); for (int i = submeshBuffers.Count; submeshBuffers.Count < targetSubmeshCount; i++) submeshBuffers.Add(new SubmeshTriangleBuffer(instructionItems[i].triangleCount)); } return submeshBuffersWasResized; } /// Fills Unity vertex data buffers with verts from the Spine Skeleton. /// Spine.Skeleton source of the drawOrder array /// Slot index of the first slot. /// The index bounding the slot list. [endSlot - 1] is the last slot to be added. /// Spacing along the z-axis between attachments. /// If set to true, vertex colors will be premultiplied. This will also enable additive. /// Vertex positions array. /// Vertex UV array. /// Vertex color array (Color32). /// A reference to the running vertex index. This is used when more than one submesh is to be added. /// A temporary vertex position buffer for attachment position values. /// Reference to the running calculated minimum bounds. /// Reference to the running calculated maximum bounds. /// Include MeshAttachments. If false, it will ignore MeshAttachments. public static void FillVerts (Skeleton skeleton, int startSlot, int endSlot, float zSpacing, bool pmaColors, Vector3[] verts, Vector2[] uvs, Color32[] colors, ref int vertexIndex, ref float[] tempVertBuffer, ref Vector3 boundsMin, ref Vector3 boundsMax, bool renderMeshes = true) { Color32 color= new Color32(); var skeletonDrawOrderItems = skeleton.DrawOrder.Items; float a = skeleton.a * 255, r = skeleton.r, g = skeleton.g, b = skeleton.b; int vi = vertexIndex; var tempVerts = tempVertBuffer; Vector3 bmin = boundsMin; Vector3 bmax = boundsMax; // drawOrder[endSlot] is excluded for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) { var slot = skeletonDrawOrderItems[slotIndex]; var attachment = slot.attachment; float z = slotIndex * zSpacing; var regionAttachment = attachment as RegionAttachment; if (regionAttachment != null) { regionAttachment.ComputeWorldVertices(slot.bone, tempVerts); float x1 = tempVerts[RegionAttachment.X1], y1 = tempVerts[RegionAttachment.Y1]; float x2 = tempVerts[RegionAttachment.X2], y2 = tempVerts[RegionAttachment.Y2]; float x3 = tempVerts[RegionAttachment.X3], y3 = tempVerts[RegionAttachment.Y3]; float x4 = tempVerts[RegionAttachment.X4], y4 = tempVerts[RegionAttachment.Y4]; verts[vi].x = x1; verts[vi].y = y1; verts[vi].z = z; verts[vi + 1].x = x4; verts[vi + 1].y = y4; verts[vi + 1].z = z; verts[vi + 2].x = x2; verts[vi + 2].y = y2; verts[vi + 2].z = z; verts[vi + 3].x = x3; verts[vi + 3].y = y3; verts[vi + 3].z = z; if (pmaColors) { color.a = (byte)(a * slot.a * regionAttachment.a); color.r = (byte)(r * slot.r * regionAttachment.r * color.a); color.g = (byte)(g * slot.g * regionAttachment.g * color.a); color.b = (byte)(b * slot.b * regionAttachment.b * color.a); if (slot.data.blendMode == BlendMode.additive) color.a = 0; } else { color.a = (byte)(a * slot.a * regionAttachment.a); color.r = (byte)(r * slot.r * regionAttachment.r * 255); color.g = (byte)(g * slot.g * regionAttachment.g * 255); color.b = (byte)(b * slot.b * regionAttachment.b * 255); } colors[vi] = color; colors[vi + 1] = color; colors[vi + 2] = color; colors[vi + 3] = color; float[] regionUVs = regionAttachment.uvs; uvs[vi].x = regionUVs[RegionAttachment.X1]; uvs[vi].y = regionUVs[RegionAttachment.Y1]; uvs[vi + 1].x = regionUVs[RegionAttachment.X4]; uvs[vi + 1].y = regionUVs[RegionAttachment.Y4]; uvs[vi + 2].x = regionUVs[RegionAttachment.X2]; uvs[vi + 2].y = regionUVs[RegionAttachment.Y2]; uvs[vi + 3].x = regionUVs[RegionAttachment.X3]; uvs[vi + 3].y = regionUVs[RegionAttachment.Y3]; if (x1 < bmin.x) bmin.x = x1; // Potential first attachment bounds initialization. Initial min should not block initial max. Same for Y below. if (x1 > bmax.x) bmax.x = x1; if (x2 < bmin.x) bmin.x = x2; else if (x2 > bmax.x) bmax.x = x2; if (x3 < bmin.x) bmin.x = x3; else if (x3 > bmax.x) bmax.x = x3; if (x4 < bmin.x) bmin.x = x4; else if (x4 > bmax.x) bmax.x = x4; if (y1 < bmin.y) bmin.y = y1; if (y1 > bmax.y) bmax.y = y1; if (y2 < bmin.y) bmin.y = y2; else if (y2 > bmax.y) bmax.y = y2; if (y3 < bmin.y) bmin.y = y3; else if (y3 > bmax.y) bmax.y = y3; if (y4 < bmin.y) bmin.y = y4; else if (y4 > bmax.y) bmax.y = y4; vi += 4; } else if (renderMeshes) { var meshAttachment = attachment as MeshAttachment; if (meshAttachment != null) { int meshVertexCount = meshAttachment.worldVerticesLength; if (tempVerts.Length < meshVertexCount) tempVerts = new float[meshVertexCount]; meshAttachment.ComputeWorldVertices(slot, tempVerts); if (pmaColors) { color.a = (byte)(a * slot.a * meshAttachment.a); color.r = (byte)(r * slot.r * meshAttachment.r * color.a); color.g = (byte)(g * slot.g * meshAttachment.g * color.a); color.b = (byte)(b * slot.b * meshAttachment.b * color.a); if (slot.data.blendMode == BlendMode.additive) color.a = 0; } else { color.a = (byte)(a * slot.a * meshAttachment.a); color.r = (byte)(r * slot.r * meshAttachment.r * 255); color.g = (byte)(g * slot.g * meshAttachment.g * 255); color.b = (byte)(b * slot.b * meshAttachment.b * 255); } float[] attachmentUVs = meshAttachment.uvs; // Potential first attachment bounds initialization. See conditions in RegionAttachment if (vi == vertexIndex) { // Initial min should not block initial max. // vi == vertexIndex does not always mean the bounds are fresh. It could be a submesh. Do not nuke old values by omitting the check. // Should know that this is the first attachment in the submesh. slotIndex == startSlot could be an empty slot. float fx = tempVerts[0], fy = tempVerts[1]; if (fx < bmin.x) bmin.x = fx; if (fx > bmax.x) bmax.x = fx; if (fy < bmin.y) bmin.y = fy; if (fy > bmax.y) bmax.y = fy; } for (int iii = 0; iii < meshVertexCount; iii += 2) { float x = tempVerts[iii], y = tempVerts[iii + 1]; verts[vi].x = x; verts[vi].y = y; verts[vi].z = z; colors[vi] = color; uvs[vi].x = attachmentUVs[iii]; uvs[vi].y = attachmentUVs[iii + 1]; if (x < bmin.x) bmin.x = x; else if (x > bmax.x) bmax.x = x; if (y < bmin.y) bmin.y = y; else if (y > bmax.y) bmax.y = y; vi++; } } } } // ref return values vertexIndex = vi; tempVertBuffer = tempVerts; boundsMin = bmin; boundsMax = bmax; } /// Fills a submesh triangle buffer array. /// Spine.Skeleton source of draw order slots. /// The target triangle count. /// First vertex of this submesh. /// Start slot. /// End slot. /// The triangle buffer array to be filled. This reference will be replaced in case the triangle values don't fit. /// If set to true, the triangle buffer is allowed to be larger than needed. public static void FillTriangles (ref int[] triangleBuffer, Skeleton skeleton, int triangleCount, int firstVertex, int startSlot, int endSlot, bool isLastSubmesh) { int trianglesCapacity = triangleBuffer.Length; int[] tris = triangleBuffer; if (isLastSubmesh) { if (trianglesCapacity > triangleCount) { for (int i = triangleCount; i < trianglesCapacity; i++) tris[i] = 0; } else if (trianglesCapacity < triangleCount) { triangleBuffer = tris = new int[triangleCount]; } } else if (trianglesCapacity != triangleCount) { triangleBuffer = tris = new int[triangleCount]; } var skeletonDrawOrderItems = skeleton.drawOrder.Items; for (int i = startSlot, n = endSlot, ti = 0, afv = firstVertex; i < n; i++) { var attachment = skeletonDrawOrderItems[i].attachment; // RegionAttachment if (attachment is RegionAttachment) { tris[ti] = afv; tris[ti + 1] = afv + 2; tris[ti + 2] = afv + 1; tris[ti + 3] = afv + 2; tris[ti + 4] = afv + 3; tris[ti + 5] = afv + 1; ti += 6; afv += 4; continue; } // MeshAttachment var meshAttachment = attachment as MeshAttachment; if (meshAttachment != null) { int[] attachmentTriangles = meshAttachment.triangles; for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, ti++) tris[ti] = afv + attachmentTriangles[ii]; afv += meshAttachment.worldVerticesLength >> 1; // length/2; } } } public static void FillTrianglesQuads (ref int[] triangleBuffer, ref int storedTriangleCount, ref int storedFirstVertex, int instructionsFirstVertex, int instructionTriangleCount, bool isLastSubmesh) { int trianglesCapacity = triangleBuffer.Length; if (isLastSubmesh && trianglesCapacity > instructionTriangleCount) { for (int i = instructionTriangleCount; i < trianglesCapacity; i++) triangleBuffer[i] = 0; storedTriangleCount = instructionTriangleCount; } else if (trianglesCapacity != instructionTriangleCount) { triangleBuffer = new int[instructionTriangleCount]; storedTriangleCount = 0; } // Use stored quad triangles if possible. int[] tris = triangleBuffer; if (storedFirstVertex != instructionsFirstVertex || storedTriangleCount < instructionTriangleCount) { //|| storedTriangleCount == 0 storedTriangleCount = instructionTriangleCount; storedFirstVertex = instructionsFirstVertex; int afv = instructionsFirstVertex; // attachment first vertex for (int ti = 0; ti < instructionTriangleCount; ti += 6, afv += 4) { tris[ti] = afv; tris[ti + 1] = afv + 2; tris[ti + 2] = afv + 1; tris[ti + 3] = afv + 2; tris[ti + 4] = afv + 3; tris[ti + 5] = afv + 1; } } } /// Creates a UnityEngine.Bounds struct from minimum and maximum value vectors. public static Bounds ToBounds (Vector3 boundsMin, Vector3 boundsMax) { Vector3 size = (boundsMax - boundsMin); return new Bounds((boundsMin + (size * 0.5f)), size); } #region TangentSolver2D // Thanks to contributions from forum user ToddRivers /// Step 1 of solving tangents. Ensure you have buffers of the correct size. /// Eventual Vector4[] tangent buffer to assign to Mesh.tangents. /// Temporary Vector2 buffer for calculating directions. /// Number of vertices that require tangents (or the size of the vertex array) public static void SolveTangents2DEnsureSize (ref Vector4[] tangentBuffer, ref Vector2[] tempTanBuffer, int vertexCount) { if (tangentBuffer == null || tangentBuffer.Length < vertexCount) tangentBuffer = new Vector4[vertexCount]; if (tempTanBuffer == null || tempTanBuffer.Length < vertexCount * 2) tempTanBuffer = new Vector2[vertexCount * 2]; // two arrays in one. } /// Step 2 of solving tangents. Fills (part of) a temporary tangent-solution buffer based on the vertices and uvs defined by a submesh's triangle buffer. Only needs to be called once for single-submesh meshes. /// A temporary Vector3[] for calculating tangents. /// The mesh's current vertex position buffer. /// The mesh's current triangles buffer. /// The mesh's current uvs buffer. /// Number of vertices that require tangents (or the size of the vertex array) /// The number of triangle indexes in the triangle array to be used. public static void SolveTangents2DTriangles (Vector2[] tempTanBuffer, int[] triangles, int triangleCount, Vector3[] vertices, Vector2[] uvs, int vertexCount) { Vector2 sdir; Vector2 tdir; for (int t = 0; t < triangleCount; t += 3) { int i1 = triangles[t + 0]; int i2 = triangles[t + 1]; int i3 = triangles[t + 2]; Vector3 v1 = vertices[i1]; Vector3 v2 = vertices[i2]; Vector3 v3 = vertices[i3]; Vector2 w1 = uvs[i1]; Vector2 w2 = uvs[i2]; Vector2 w3 = uvs[i3]; float x1 = v2.x - v1.x; float x2 = v3.x - v1.x; float y1 = v2.y - v1.y; float y2 = v3.y - v1.y; float s1 = w2.x - w1.x; float s2 = w3.x - w1.x; float t1 = w2.y - w1.y; float t2 = w3.y - w1.y; float div = s1 * t2 - s2 * t1; float r = (div == 0f) ? 0f : 1f / div; sdir.x = (t2 * x1 - t1 * x2) * r; sdir.y = (t2 * y1 - t1 * y2) * r; tempTanBuffer[i1] = tempTanBuffer[i2] = tempTanBuffer[i3] = sdir; tdir.x = (s1 * x2 - s2 * x1) * r; tdir.y = (s1 * y2 - s2 * y1) * r; tempTanBuffer[vertexCount + i1] = tempTanBuffer[vertexCount + i2] = tempTanBuffer[vertexCount + i3] = tdir; } } /// Step 3 of solving tangents. Fills a Vector4[] tangents array according to values calculated in step 2. /// A Vector4[] that will eventually be used to set Mesh.tangents /// A temporary Vector3[] for calculating tangents. /// Number of vertices that require tangents (or the size of the vertex array) public static void SolveTangents2DBuffer (Vector4[] tangents, Vector2[] tempTanBuffer, int vertexCount) { Vector4 tangent; tangent.z = 0; for (int i = 0; i < vertexCount; ++i) { Vector2 t = tempTanBuffer[i]; // t.Normalize() (aggressively inlined). Even better if offloaded to GPU via vertex shader. float magnitude = Mathf.Sqrt(t.x * t.x + t.y * t.y); if (magnitude > 1E-05) { float reciprocalMagnitude = 1f/magnitude; t.x *= reciprocalMagnitude; t.y *= reciprocalMagnitude; } Vector2 t2 = tempTanBuffer[vertexCount + i]; tangent.x = t.x; tangent.y = t.y; //tangent.z = 0; tangent.w = (t.y * t2.x > t.x * t2.y) ? 1 : -1; // 2D direction calculation. Used for binormals. tangents[i] = tangent; } } #endregion #region SubmeshTriangleBuffer public class SubmeshTriangleBuffer { public int[] triangles; public int triangleCount; // for last/single submeshes with potentially zeroed triangles. public int firstVertex = -1; // for !renderMeshes. public SubmeshTriangleBuffer () { } public SubmeshTriangleBuffer (int triangleCount) { triangles = new int[triangleCount]; } } #endregion } }